#include "frustum.h"
#include <vector>
#include <iostream>

Frustum::Frustum(Point eye,Point at,Point up,float fovUp,float aspect,float dNear,float dFar){
	// Fuerzo a que se calculen los puntos
	updateFrustum(eye,at,up,fovUp,aspect,dNear,dFar);
}

Frustum::~Frustum(){

}

void Frustum::updateFrustum(Point eye,Point at,Point up,float fovUp,float aspect,float dNear,float dFar){
	_up=up;

	// Calculo los 8 puntos del frustum
	Point dir  = at-eye;
	dir = dir*(1/dir.mod());
	// Vector normal al vector direccion de la camara y al up vector
	Point nDir = up.cross(dir);
	nDir = nDir*(1/nDir.mod());
	// Este seria el verdadero up( el otro solo sirve para saber para que lado es arriba en la camara )
	up = (dir.cross(nDir));
	up = up*(1/up.mod());

	float val = tan(fovUp/2);
	_vertices[0] = eye + dir*dNear + up*(val*dNear) - nDir*(val*dNear*aspect);
	_vertices[1] = eye + dir*dNear + up*(val*dNear) + nDir*(val*dNear*aspect);
	_vertices[2] = eye + dir*dNear - up*(val*dNear) + nDir*(val*dNear*aspect);
	_vertices[3] = eye + dir*dNear - up*(val*dNear) - nDir*(val*dNear*aspect);

	_vertices[4]  = eye + dir*dFar + up*(val*dFar) - nDir*(val*dFar*aspect);
	_vertices[5]  = eye + dir*dFar + up*(val*dFar) + nDir*(val*dFar*aspect);
	_vertices[6]  = eye + dir*dFar - up*(val*dFar) + nDir*(val*dFar*aspect);
	_vertices[7]  = eye + dir*dFar - up*(val*dFar) - nDir*(val*dFar*aspect);


	calculateNormals(_normals);

	_eye=eye;
	_at=at;
}

void Frustum::calculateNormals(Point* normals){
	// farPlane normal
	normals[0]  = (_vertices[5]-_vertices[4]).cross((_vertices[4]-_vertices[6]));
	// leftPlane normal
	normals[2]  = (_vertices[1]-_vertices[2]).cross(_vertices[5]-_vertices[1]);
	// topPlane normal
	normals[4]  = (_vertices[0]-_vertices[1]).cross(_vertices[5]-_vertices[1]);
	// nearPlane normal
	normals[1]  = (_vertices[0]-_vertices[1]).cross((_vertices[0]-_vertices[2]));
	// rightPlane normal
	normals[3]  = (_vertices[3]-_vertices[0]).cross(_vertices[4]-_vertices[0]);
	// bottomPlane normal
	normals[5]  = (_vertices[2]-_vertices[3]).cross(_vertices[6]-_vertices[2]);

	//normalize
	for(unsigned int c=0;c<6;c++)
		normals[c] = normals[c]*(1/normals[c].mod());
}

bool Frustum::intersect(const Cube& cube)const{
	// Se trata de ver si el objeto esta fuera del frustum,
	// esto sucede si sus vertices estan completamente detras de algun plano.
	// Caso contrario la caja esta total o parcialmente adentro o la caja contiene
	// al frusutm.

	unsigned int pointsIndex[6] = { 4,1,1,3,1,3 };

	// Cuento la cantidad de vertices que estan dentro del frustum
	int totalVerticesIn = 0;

	const Point* boxVertices = cube.frontFace();

	// Para cada plano del frustum
	for(unsigned int j=0;j<6;j++){
		int vertexInCount = 8; // Supongo que estan todos adentro al comienzo
		int isIn = 1; // Supongo que esta adentro

		// Para cada vertice de la caja
		for(unsigned int i=0;i<8;i++){
			Point p= boxVertices[i]-_vertices[pointsIndex[j]];
			float val = (p*_normals[j]);

			if(val>=0){
				isIn = 0;
				vertexInCount--;
			}
		}

		// El objeto esta totalmente detras de un plano
		// por lo tanto esta fuera del frustum
		if(!vertexInCount)
			return false;

		totalVerticesIn += isIn;
	}

	// La caja o al menos uno de sus vertices esta dentro del frustum(o lo contiene).
	return true;
}

Point Frustum::getEye(){
	return _eye;
}

Point Frustum::getAt(){
	return _at;
}

Point Frustum::getUp(){
	return _up;
}

void Frustum::applyTransformation(const Matrix4x4& m){
	for(unsigned int c=0;c<8;c++){
		_vertices[c]=m*_vertices[c];
	}
	_eye=m*_eye;
	_at=m*_at;

	calculateNormals(_normals);
}

Point Frustum::getFrustumUp(){
	return (_vertices[3]-_vertices[0]);
}

void  Frustum::setUp(const Point& up){
	_up=up;
}

void Frustum::transformUp(const Matrix4x4& m){
	_up=m*_up;
}
